class Parser
token
  NUM ID DEF
  READ WRITE
  PLUS MINUS MUL DIV MOD
  SEMI NL LPAR RPAR
  WHILE IF ELSE ELSIF END
  EQ NE GT GE LT LE
  ET OU NON VRAI FAUX
  COMMA DOLLAR
  FUNCTION
  ARRAY LBRA RBRA PIPE
  GOTO LABEL
rule
  qq: bb { @lvars={}; @ln=0; @fid=nil; @tlabs={} } t {
    @funcs.each {|k,v| raise "function #{k} is not defined" unless v[1]}
    @tlabs.each {|k,v| raise "label #{k} is not defined in MAIN" unless v }

    acode=Code.new
    @arrs.each {|k,v|
      raise "array #{k} is not declared" unless v

      @macros["A#{k}"]=SYSREGS.size+@gvars.size+acode.size-1
      if v.kind_of?(Integer)
        @macros["AS#{k}"]=v
        v.times { acode.push 'con 0' }
      else
        @macros["AS#{k}"]=v.size
        v.each {|i| acode.push "con #{i}" }
      end
    }

    code=Code.new
    code.push 'sal '+(SYSREGS.size+@gvars.size).to_s
    code.push acode
    code.push 'jmp LMAIN'
    code.push val[0]
    code.push 'LMAIN'
    code.push 'con '+(SYSREGS.size+@gvars.size+acode.size).to_s
    code.push 'gto RS'
    code.push 'sal '+@lvars.size.to_s
    val[2].cnum+=@lvars.size; code.push val[2]
    code.push 'hal 0'

    result=[code.addCON,@macros]
}

  bb: bb aa {result.push val[1]}
   | bb adecl
   | {result=Code.new}

  aa: FUNCTION fid LPAR dparams RPAR sep t END sep
       { 
         @tlabs.each {|k,v| raise "label #{k} is not defined in #{@fid}" unless v }

         pnum=val[3].size
         if @funcs[val[1]]
           raise "function #{val[1]} is already defined" if @funcs[val[1]][1]
           raise "argument number error (#{val[1]})" unless pnum==@funcs[val[1]][0]
           @funcs[val[1]][1]=true
         else
           @funcs[val[1]]=[pnum,true]
         end

         result=Code.new
         result.push 'L!'+val[1],'sal '+(@lvars.size-1-pnum).to_s
         val[6].cnum+=@lvars.size-1-pnum; result.push val[6]
         (@lvars.size-1).times { result.push 'pop' }
         SYSREGS.reverse_each {|i| result.push 'gto '+i }
         result.push 'jpo'
       }

  fid: ID { @fid=val[0]; @lvars={@fid=>0}; @ln=0; @tlabs={} }

  dparams: dparam {
      @lvars[@fid]=-(3+SYSREGS.size+val[0].size)
      val[0].each_index {|i| @lvars[val[0][i]]=i-val[0].size}
    }
   | { result=[]; @lvars[@fid]=-(3+SYSREGS.size) }

  dparam: dparam COMMA ID {
    if @lvars.include?(val[2])
      raise "variable #{val[2]} already exists"
    end
    result.push val[2]
    @lvars[val[2]]=0
  }
  | ID {
    if @lvars.include?(val[0])
      raise "variable #{val[0]} already exists"
    end
    result=[val[0]]
    @lvars[val[0]]=0
  }

  adecl: ARRAY ID LBRA anum RBRA sep {
      if @arrs[val[1]]
        raise "array #{val[1]} is already declared"
      else
        raise 'array size must be positive' unless val[3]>0
        @arrs[val[1]]=val[3]
      end
    }
   | ARRAY ID DEF LBRA alist RBRA sep {
      if @arrs[val[1]]
        raise "array #{val[1]} is already declared"
      else
        @arrs[val[1]]=val[4]
      end
    }

  alist: alist COMMA anum { result.push val[2] }
   | anum { result=[val[0]] }

  anum: NUM { result=val[0].to_i }
   | MINUS NUM { result=-(val[1].to_i) }
   | PLUS NUM { result=val[1].to_i }

  t: t s { result.push val[1] }
   | { result=Code.new }

  s: WRITE e sep { result=val[1].push 'pri' }
   | ID DEF e sep { unless k=@lvars[val[0]]
                      k=@lvars[val[0]]=@ln
                      @ln+=1
                    end
                    result=val[2].push 'con '+k.to_s
                    result.push 'god RS'
                    result.push 'add'
                    result.push 'gto'
                  }
   | DOLLAR ID DEF e sep { 
                    unless k=@gvars.index(val[1])
                      @gvars.push(val[1])
                      k=@gvars.size-1
                    end
                    result=val[3].push 'gto '+(SYSREGS.size+k).to_s
                  }
   | ID LBRA e RBRA DEF e sep {
       @arrs[val[0]]=nil unless @arrs.key?(val[0])
       result=val[5]
       result.push 'con A'+val[0]
       val[2].cnum+=2; result.push val[2]
       l0=@lab.next; l1=@lab.next;
       result.push 'gto RC0'
       result.push 'god RC0'
       result.push 'jpo '+l1
       result.push l0
       result.push 'hal 1'
       result.push l1
       result.push 'god RC0'
       result.push 'con AS'+val[0]
       result.push 'sub'
       result.push 'jpo '+l0
       result.push 'god RC0'
       result.push 'add'
       result.push 'gto'
     }
   | READ ID sep { unless k=@lvars[val[1]]
                     k=@lvars[val[1]]=@ln
                     @ln+=1
                   end
                   result=Code.new
                   result.push 'red', 'con '+k.to_s
                   result.push 'god RS'
                   result.push 'add'
                   result.push 'gto'
                 }
   | READ DOLLAR ID sep { 
                    unless k=@gvars.index(val[2])
                      @gvars.push(val[2])
                      k=@gvars.size-1
                    end
                    result=Code.new.push 'red', 'gto '+(SYSREGS.size+k).to_s
                  }
   | READ ID LBRA e RBRA sep {
       @arrs[val[1]]=nil unless @arrs.key?(val[1])
       result=Code.new
       result.push 'red'
       result.push 'con A'+val[1]
       val[3].cnum+=2; result.push val[3]
       l0=@lab.next; l1=@lab.next;
       result.push 'gto RC0'
       result.push 'god RC0'
       result.push 'jpo '+l1
       result.push l0
       result.push 'hal 1'
       result.push l1
       result.push 'god RC0'
       result.push 'con AS'+val[1]
       result.push 'sub'
       result.push 'jpo '+l0
       result.push 'god RC0'
       result.push 'add'
       result.push 'gto'
     }
   | WHILE conds sep t END sep {
                               l0=@lab.next; l1=@lab.next
                               result=Code.new
                               result.push l0
                               result.push val[1]
                               result.push 'jpo '+l1
                               result.push val[3]
                               result.push 'jmp '+l0
                               result.push l1
                             }
   | func sep { result.push 'pop' }
   | ifs
   | GOTO ID sep {
       @tlabs[val[1]]=false unless @tlabs.key?(val[1])
       result=Code.new
       result.push "jmp L#{@fid ? '!'+@fid : 'MAIN'}@#{val[1]}"
     }
   | LABEL ID sep {
       raise "label #{val[1]} is already appeared in #{@fid ? @fid : 'MAIN'}" if @tlabs[val[1]]
       @tlabs[val[1]]=true
       result=Code.new
       result.push "L#{@fid ? '!'+@fid : 'MAIN'}@#{val[1]}"
     }

   ifs: ifss END sep {
       result=val[0][0].push val[0][1]
     }
   | ifss ELSE sep t END sep {
       result=val[0][0]
       result.push val[3]
       result.push val[0][1]
     }

  ifss: IF conds sep t {
       lend=@lab.next; l1=@lab.next
       code=val[1]
       code.push 'jpo '+l1
       code.push val[3]
       code.push 'jmp '+lend
       code.push l1
       result=[code,lend]
     }
   | ifss ELSIF conds sep t {
       code,lend=result
       l1=@lab.next
       code.push val[2]
       code.push 'jpo '+l1
       code.push val[4]
       code.push 'jmp '+lend
       code.push l1
     }

  sep: SEMI
   | NL
   | SEMI NL

  conds: conds OU et {
    l1=@lab.next; l2=@lab.next
    result.push 'jpo '+l1
    result.push 'con  0'
    result.push 'jmp '+l2
    result.push l1
    result.push val[2]
    result.push l2
  }
    | et

  et: et ET cond {
    l1=@lab.next; l2=@lab.next
    result.push 'jpo '+l1
    result.push val[2]
    result.push 'jmp '+l2
    result.push l1
    result.push 'con 1'
    result.push l2
  }
    | cond

  cond: e EQ e { l1=@lab.next; l2=@lab.next
                 val[2].cnum+=1; result.push val[2]
                 result.push 'sub'
                 result.push 'gto RC0'
                 result.push 'god RC0'
                 result.push 'jpo '+l1
                 result.push 'con 0'
                 result.push 'god RC0'
                 result.push 'sub'
                 result.push 'jmp '+l2
                 result.push l1
                 result.push 'con 1'
                 result.push l2
               }
   | e NE e { l1=@lab.next; l2=@lab.next
              val[2].cnum+=1; result.push val[2]
              result.push 'sub'
              result.push 'gto RC0'
              result.push 'god RC0'
              result.push 'jpo '+l1
              result.push 'god RC0'
              result.push 'con 1'
              result.push 'add'
              result.push 'jmp '+l2
              result.push l1
              result.push 'con 0'
              result.push l2
            }
   | e GT e { result=val[2]
              val[0].cnum+=1; result.push val[0]
              result.push 'sub'
              result.push 'con 1'
              result.push 'add'
            }
   | e GE e { result=val[2]
              val[0].cnum+=1; result.push val[0]
              result.push 'sub'
            }
   | e LT e { val[2].cnum+=1; result.push val[2]
              result.push 'sub'
              result.push 'con 1'
              result.push 'add'
            }
   | e LE e { val[2].cnum+=1; result.push val[2]
              result.push 'sub'
            }
   | NON cond {
       result=Code.new
       result.push 'con 1'
       val[1].cnum+=1; result.push val[1]
       result.push 'sub'
     }
   | LPAR conds RPAR { result=val[1] }
   | VRAI { result=Code.new.push('con 0') }
   | FAUX { result=Code.new.push('con 1') }

  e: e PLUS f { val[2].cnum+=1; result.push val[2],'add' }
   | e MINUS f { val[2].cnum+=1; result.push val[2],'sub' }
   | f

  f: f MUL g { val[2].cnum+=1; result.push val[2],'mul' }
   | f DIV g { val[2].cnum+=1; result.push val[2],'div' }
   | f MOD g {
       result.push 'gto RC0'
       result.push 'god RC0'
       result.push 'god RC0'
       val[2].cnum+=2; result.push val[2]
       result.push 'gto RC0'
       result.push 'god RC0'
       result.push 'div'
       result.push 'god RC0'
       result.push 'mul'
       result.push 'sub'
     }
   | g

  g: NUM { result=Code.new.push 'con '+val[0] }
   | MINUS g {
      if val[1].size==1 and val[1][0]=~/\Acon\s+(-?\d+)\z/
        val[1][0]="con #{-($~[1].to_i)}"
        result=val[1]
      else
        val[1].cnum+=1; result=Code.new.push 'con 0',val[1],'sub'
      end
    }
   | PLUS g { result=val[1] }
   | ID { unless k=@lvars[val[0]]
            k=@lvars[val[0]]=@ln
            @ln+=1
          end
          result=Code.new.push 'con '+k.to_s
          result.push 'god RS'
          result.push 'add'
          result.push 'god'
        }
   | DOLLAR ID {
       unless k=@gvars.index(val[1])
         @gvars.push(val[1])
         k=@gvars.size-1
       end
       result=Code.new.push 'god '+(SYSREGS.size+k).to_s
     }
   | ID LBRA e RBRA {
       @arrs[val[0]]=nil unless @arrs.key?(val[0])
       result=Code.new.push 'con A'+val[0]
       val[2].cnum+=1; result.push val[2]
       l0=@lab.next; l1=@lab.next;
       result.push 'gto RC0'
       result.push 'god RC0'
       result.push 'jpo '+l1
       result.push l0
       result.push 'hal 1'
       result.push l1
       result.push 'god RC0'
       result.push 'con AS'+val[0]
       result.push 'sub'
       result.push 'jpo '+l0
       result.push 'god RC0'
       result.push 'add'
       result.push 'god'
     }
   | PIPE ID PIPE {
       @arrs[val[1]]=nil unless @arrs.key?(val[1])
       result=Code.new.push 'con AS'+val[1]
     }
   | LPAR e RPAR { result=val[1] }
   | func

  func: ID LPAR params RPAR {
         k=val[2].size
         if @funcs[val[0]]
           raise "argument number error (#{val[0]})" unless k==@funcs[val[0]][0]
         else
           @funcs[val[0]]=[k,false]
         end

         l0=@lab.next
         result=Code.new.push 'con 0', 'con 1'
         result.push 'con '+l0
         SYSREGS.each {|j| result.push 'god '+j }
         result.push val[2]
         result.push 'god RS'
         c=Code.new.push 'CON'; c.cnum+=3+SYSREGS.size+k; result.push c
         result.push 'add'
         result.push 'gto RS'
         result.push 'jmp L!'+val[0]
         result.push l0
       }

  params: param
   | { result=Code.new }

  param: param COMMA e { val[2].cnum+=val[0].size; result.push val[2] }
   | e { result=Code.new.push val[0]; result.cnum+=3+SYSREGS.size }
end
---- header
class Code < Array
  def initialize
    super
    @cnum=0
  end
  attr_accessor :cnum

  def addCON(k=0)
    out=[]
    k=k+@cnum

    each {|a|
      if a.instance_of?(Code)
        out.concat a.addCON(k)
      elsif a=='CON'
        out.push "con #{k}"
      else
        out.push a
      end
    }

    out
  end
end

class Label
  def initialize
    @i=-1
  end

  def next
    @i+=1
    "L#{@i}"
  end
end
---- inner
SYSREGS=['RS', 'RC0']

def parse(lex)

  @lex=lex
  @lab=Label.new
  @gvars=[]
  @funcs={}
  @arrs={}
  @macros={}
  (0...SYSREGS.size).each do |i|
    @macros[SYSREGS[i]]=i
  end

  do_parse
end

def next_token
  @lex.next
end
---- footer
require 'strscan'
class Lexer
  def initialize(file)
    f=open(file)

    @t=[]
    s=StringScanner.new('')
    while str=f.gets
      s.string=str
      a=[]
      until s.eos?
        case
        when s.scan(/\#/)
          s.terminate
        when s.scan(/\s+/)
        when s.scan(/[a-z][a-z0-9]*/)
          case s.matched
          when 'function'
            a.push [:FUNCTION,0]
          when 'write'
            a.push [:WRITE,0]
          when 'read'
            a.push [:READ,0]
          when 'while'
            a.push [:WHILE,0]
          when 'if'
            a.push [:IF,0]
          when 'else'
            a.push [:ELSE,0]
          when 'elf'
            a.push [:ELSIF,0]
          when 'end'
            a.push [:END,0]
          when 'array'
            a.push [:ARRAY,0]
          when 'et'
            a.push [:ET,0]
          when 'ou'
            a.push [:OU,0]
          when 'non'
            a.push [:NON,0]
          when 'goto'
            a.push [:GOTO,0]
          when 'label'
            a.push [:LABEL,0]
          when 'vrai'
            a.push [:VRAI,0]
          when 'faux'
            a.push [:FAUX,0]
          else
            a.push [:ID,s[0]]
          end
        when s.scan(/0*(\d+)/)
          a.push [:NUM,s[1]]
        when s.scan(/:=/)
          a.push [:DEF,0]
        when s.scan(/></)
          a.push [:NE,0]
        when s.scan(/>=/)
          a.push [:GE,0]
        when s.scan(/<=/)
          a.push [:LE,0]
        when s.scan(/=/)
          a.push [:EQ,0]
        when s.scan(/>/)
          a.push [:GT,0]
        when s.scan(/</)
          a.push [:LT,0]
        when s.scan(/\+/)
          a.push [:PLUS,0]
        when s.scan(/\-/)
          a.push [:MINUS,0]
        when s.scan(/\*/)
          a.push [:MUL,0]
        when s.scan(/\//)
          a.push [:DIV,0]
        when s.scan(/%/)
          a.push [:MOD,0]
        when s.scan(/;/)
          a.push [:SEMI,0]
        when s.scan(/,/)
          a.push [:COMMA,0]
        when s.scan(/\$/)
          a.push [:DOLLAR,0]
        when s.scan(/\(/)
          a.push [:LPAR,0]
        when s.scan(/\)/)
          a.push [:RPAR,0]
        when s.scan(/\[/)
          a.push [:LBRA,0]
        when s.scan(/\]/)
          a.push [:RBRA,0]
        when s.scan(/\|/)
          a.push [:PIPE,0]
        else
          raise 'unknown token at "'+s.string.chomp+'"'
        end
      end

      unless a.empty?
        @t.concat a
        @t.push [:NL,0]
      end
    end

    f.close
  end

  def next
    @t.empty? ? [false,0] : @t.shift
  end
end

class Assembler
  def assemble(code,mac)
    out=[]

    mac.each_key {|k|
      raise "macro name error (#{k})" unless k=~/\A[A-KM-Z]\S*\z/
    }

    code.each {|u|
      unless u=~/\A([a-z]{3})\s+(.+)\z/
        out.push u
        next
      end

      m2=$~[2]
      case $~[1]
      when 'sal'
        m2=mac[m2] if mac.key? m2
        raise "cannot expand '#{u}'" unless m2=~/\A\+?\d+\z/
        m2.to_i.times { out.push 'con 0' }
      when 'god'
        out.push 'con '+m2
        out.push 'god'
      when 'gto'
        out.push 'con '+m2
        out.push 'gto'
      when 'jpo'
        out.push 'con '+m2
        out.push 'jpo'
      when 'jmp'
        out.push 'con 1'
        out.push 'con '+m2
        out.push 'jpo'
      when 'hal'
        out.push 'con '+m2
        out.push 'hal'
      else
        out.push u
      end
    }

    rep=[]
    j=nil
    lt=[]
    out.each_index {|i|
      if out[i]=~/\AL\S*\z/
        raise "label #{$&} is already appeared" if lt.include?($&)
        lt << $&

        if j
          rep.push [out[i],j]
          out[i]=nil
        else
          j=out[i]
        end
      else
        j=nil
      end
    }  
    out.compact!
    rep.each {|f,t|
      rex=/(\A|\s)#{Regexp.escape(f)}(?=\z|\s)/
      out.collect! {|a|
        a.gsub(rex) { $1+t }
      }
    }
    (3...out.size).each {|i|
      if out[i-3]=='con 1' and out[i-2]=~/\Acon\s+(L\S*)\z/ and out[i-1]=='jpo' and out[i]==$~[1]
        out[i-3..i-1]=nil
      end
    }
    out.compact!

    j=0
    for i in 0...out.size
      if out[i]=~/\AL\S*\z/
        mac[$&]=j
        out[i]=nil
      else
        j+=1
      end
    end
    out.compact!

    out.collect! do |a|
      a.gsub(/(\A|\s)([A-Z]\S*)/) { mac.include?($2) ? $1+mac[$2].to_s: $& }
    end

    out.collect! do |a|
      a=~/\Acon\s+([\-+]?\d+)\z/ ? $~[1] : a
    end
  end
end

if ARGV.size==1
  fin=ARGV[0]
  fout='epro.code'
elsif ARGV.size==3 and ARGV[1]=='-o'
  fin=ARGV[0]
  fout=ARGV[2]
elsif ARGV.size==3 and ARGV[0]=='-o'
  fin=ARGV[2]
  fout=ARGV[1]
else
  abort "usage: ruby #{$0} source [-o code]"
end


begin
  code,mac=Parser.new.parse(Lexer.new(fin))
  prog=Assembler.new.assemble(code,mac)
  open(fout,'w') {|f|
    prog.each {|i| f.puts i}
  }
rescue
  abort $!
end
